
Public Module unhandledException

    Public Delegate Sub UnhandledExceptionDelegate(ByVal ex As Exception)
    Private domainUnhandledExceptionHandlers(-1) As UnhandledExceptionDelegate

    Private Sub addUnhandledExceptionHandler(ByRef handlers() As UnhandledExceptionDelegate _
                                            , ByVal handler As UnhandledExceptionDelegate)
        If Not handler Is Nothing Then
            ReDim Preserve handlers(handlers.Length())
            handlers(handlers.Length() - 1) = handler
        End If
    End Sub

    Private Class handlerInfo
        Public handler As UnhandledExceptionDelegate = Nothing
        Public ex As Exception = Nothing

        Public Sub New(ByVal handler As UnhandledExceptionDelegate, ByVal ex As Exception)
            Me.handler = handler
            Me.ex = ex
        End Sub
    End Class

    Private Sub handler(ByVal i As Object)
        Dim info As handlerInfo = Nothing
        info = convertor.cast(Of handlerInfo)(i)

        assert(Not info.handler Is Nothing, "info.handler is nothing.")
        info.handler(info.ex)
    End Sub

    Private Sub runUnhandledExceptionHandlers(ByVal handlers() As UnhandledExceptionDelegate, ByVal ex As Exception)
        On Error Resume Next
        'avoid to break anything here, as it's the final stage to catch any error

#If CANNOTDETERMIND Then
        'freeze other threads first
        Dim proc As Process = Nothing
        proc = Process.GetCurrentProcess()
        For i = 0 To proc.Threads().Count() - 1
            proc.Threads(i).PriorityLevel = ThreadPriorityLevel.Idle
        Next
        Threading.Thread.CurrentThread().Priority = Threading.ThreadPriority.Highest
#End If

        Threading.Thread.CurrentThread().Priority = Threading.ThreadPriority.Highest
        Dim i As Int32
        For i = 0 To handlers.Length() - 1
            If Not handlers(i) Is Nothing Then
                Dim thread As Threading.Thread = Nothing
                thread = startThread(AddressOf handler, New handlerInfo(handlers(i), ex))
                If Not thread Is Nothing Then
                    'wait for 60 sec to let a handler finish, otherwise kill it to avoid breaking shutdown/restart logic
                    thread.Priority = Threading.ThreadPriority.Highest
                    thread.Join(secondsToMilliseconds(60))
                    thread.Priority = Threading.ThreadPriority.Normal
                End If
#If PocketPC OrElse Smartphone Then
                thread.Abort()
#Else
                If thread.IsAlive() Then
                    thread.Abort()
                End If
#End If
            End If
        Next
    End Sub

    Private Sub runUnhandledExceptionHandlers(ByVal ex As Exception)
#If Not (PocketPC OrElse Smartphone) Then
        runUnhandledExceptionHandlers(threadUnhandledExceptionHandlers, ex)
#End If
        runUnhandledExceptionHandlers(domainUnhandledExceptionHandlers, ex)
    End Sub

    Public Sub registerUnhandledException(Optional ByVal handler As UnhandledExceptionDelegate = Nothing)
        registerDomainUnhandledException(handler)
#If Not (PocketPC OrElse Smartphone) Then
        registerThreadUnhandledException(handler)
#End If
    End Sub

    Public Sub registerDomainUnhandledException(Optional ByVal handler As UnhandledExceptionDelegate = Nothing)
        AddHandler AppDomain.CurrentDomain.UnhandledException, domainUnhandledExceptionDelegate
        addUnhandledExceptionHandler(domainUnhandledExceptionHandlers, handler)
    End Sub

    Dim domainUnhandledExceptionDelegate As UnhandledExceptionEventHandler = AddressOf domainUnhandledException

    Public Sub domainUnhandledException(ByVal sender As Object, ByVal arg As UnhandledExceptionEventArgs)
        Dim ex As Exception = convertor.cast(Of Exception)(arg.ExceptionObject)
        unhandledExceptionHandler(ex, "domain")
    End Sub

#If Not (PocketPC OrElse Smartphone) Then

    Private threadUnhandledExceptionHandlers(-1) As UnhandledExceptionDelegate

    Public Sub registerThreadUnhandledException(Optional ByVal handler As UnhandledExceptionDelegate = Nothing)
        AddHandler Windows.Forms.Application.ThreadException, threadUnhandledExceptionDelegate
        addUnhandledExceptionHandler(threadUnhandledExceptionHandlers, handler)
    End Sub

    Dim threadUnhandledExceptionDelegate As Threading.ThreadExceptionEventHandler = AddressOf threadUnhandledException

    Public Sub threadUnhandledException(ByVal sender As Object, ByVal arg As Threading.ThreadExceptionEventArgs)
        Dim ex As Exception = arg.Exception
        unhandledExceptionHandler(ex, "thread")
    End Sub

#End If

    Public Sub unhandledExceptionHandler(ByVal ex As Exception, Optional ByVal exceptionType As String = Nothing)
        unhandledExceptionMessage(ex, exceptionType)
        runUnhandledExceptionHandlers(ex)
        errorHandle.finalize()
        forceQuit()
    End Sub

    Public Sub unhandledExceptionMessage(ByVal ex As Exception, Optional ByVal exceptionType As String = Nothing)
        Dim msg As String = Nothing
        msg = "unhandled " + exceptionType + " exception occured," + newline.incode
#If Not (PocketPC OrElse Smartphone) Then
        msg += "source " + ex.Source + newline.incode
#End If
        msg += "message " + ex.Message + newline.incode
        msg += "stacktrace " + newline.incode + ex.StackTrace + newline.incode
        raiseError(msg, errorHandle.errorType.critical)
        'force flush, as it may not be able to wait flush for next round
        errorHandle.flush()
    End Sub

End Module
